﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Ccr.Core;
using Microsoft.Ccr.Core.Arbiters;
using XcoAppSpaces.Contracts.Messaging;
using XcoAppSpaces.Contracts.CcrExtensions;

namespace XcoAppSpaces.Core.CcrWiring.CcrRemote
{
	/// <summary>
	/// Base class for special Ccr tasks that can send data to a remote worker or port instance.
	/// </summary>
	abstract class RemotableTask : TaskCommon, IRemotableTask
	{
		protected readonly Handler<MessageToSend> messageProcessor;
		protected readonly IPort assignedPort;
		public string RemoteAddress { get; private set; }
		public string CommServiceName { get; private set; }
        public Guid PortGuid { get; private set; }
		public MessageTransferMode Mode { get; private set; }
		
		private IPortElement executionParameter;

		protected RemotableTask(Handler<MessageToSend> messageProcessor, string remoteAddress, string commServiceName, Guid portGuid, IPort assignedPort, MessageTransferMode mode)
		{
			this.messageProcessor = messageProcessor;
			this.RemoteAddress = remoteAddress;
            this.CommServiceName = commServiceName;
			this.PortGuid = portGuid;
			this.assignedPort = assignedPort;
			this.Mode = mode;
		}

		internal Receiver CreateReceiver(IPort port, MessageTransferMode transferMode)
		{
			return new Receiver((transferMode == MessageTransferMode.Parallel), (IPortReceive)port, PartialClone(port, transferMode));
		}

		public override IEnumerator<ITask> Execute()
		{
			ProcessItem(this.executionParameter.Item);
			return null;
		}

		public override IPortElement this[int index]
		{
			get
			{
				if (index != 0)
					throw new ArgumentException("parameter out of range", "index");
				return this.executionParameter;
			}
			set
			{
				if (index != 0)
					throw new ArgumentException("parameter out of range", "index");
				this.executionParameter = value;
			}
		}

		public override int PortElementCount
		{
			get { return 1; }
		}

		public void Send(object item, Port<Exception> commErrorPort, IPort commAckPort)
		{
			if (!(commAckPort is Port<Ack>))
				throw new ArgumentException("Port must be of type Port<Ack>!", "commAckPort");
			messageProcessor(new MessageToSend(RemoteAddress, CommServiceName, GetCausalityContext(), CreateMessage(item), commErrorPort, (Port<Ack>)commAckPort));
		}

		private void ProcessItem(object item)
		{
			try
			{
				messageProcessor(new MessageToSend(RemoteAddress, CommServiceName, GetCausalityContext(), CreateMessage(item), null, null));
			}
			finally
			{
				if (this.Mode == MessageTransferMode.Sequential)
				{
					//re-register task at port because sequential processing is done through non-persistent tasks
					assignedPort.ReceiveWithRemotableTask(this.TaskQueue, this);
				}
			}
		}

		private static CausalityContext GetCausalityContext()
		{
			var causalities = Dispatcher.ActiveCausalities.Select(c => new CausalityInfo(c)).ToArray();
			return (causalities.Length > 0 ? new CausalityContext(causalities) : null);
		}

		/// <summary>
		/// Method that must be implemented by base classes to create a message
		/// that should be sent from an item that has been posted to the port
		/// where this task is registered.
		/// </summary>
		/// <param name="value">The item that has been posted.</param>
		/// <returns>The message that should be sent.</returns>
		protected abstract RemoteMessage CreateMessage(object value);

		/// <summary>
		/// Partially clones the task, while using the given port and transfer mode.
		/// </summary>
		/// <param name="assignedPort"></param>
		/// <param name="mode"></param>
		/// <returns></returns>
		public abstract RemotableTask PartialClone(IPort assignedPort, MessageTransferMode mode);
	}
}
